//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace WorkflowApplicationServices
{
    using System;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.DataLayer;
    using WorkflowApplication.Interfaces;
    using System.Collections.ObjectModel;
    using System.Collections.Generic;
    using System.Linq;
    using WorkflowApplicationModels;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;

    /// <summary>
    /// The service for registry access.
    /// </summary>
    public class RegistryService : IRegistryService
    {
        #region Private variables

        /// <summary>
        /// This is used to access registry connection.
        /// </summary>
        private Connection registryConnection;

        /// <summary>
        /// System user name.
        /// </summary>
        private const string SystemUserName = "System";

        /// <summary>
        /// The system user object.
        /// </summary>
        private User systemUserObject;

        /// <summary>
        /// Holds the current user.
        /// </summary>
        private User currentUser;

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="registryConnection">The registry connection.</param>
        public RegistryService(Connection registryConnection)
        {
            this.registryConnection = registryConnection;
            this.systemUserObject = this.GetUser(RegistryService.SystemUserName);

            // Check whether user exist or not if not then create new user in registry.
            // CategoriesComposer.CreateUser(Environment.UserName, this.registryConnection);
            this.currentUser = TridentAuthentication.LoggedInUser; //this.GetUser(Environment.UserName);
        }

        #endregion Constructor

        #region IRegistryService Members

        /// <summary>
        /// Gets or sets the current connection.
        /// </summary>
        /// <value>The current connection.</value>
        public Connection CurrentConnection
        {
            get
            {
                return this.registryConnection;
            }
            set
            {
                this.registryConnection = value;
            }
        }

        /// <summary>
        /// Get the workflow category root.
        /// </summary>
        /// <returns>Workflow root category.</returns>
        public void GetWorkflowCategories(Collection<Namespace> categories)
        {
            try
            {
                Namespace wfRoot = this.systemUserObject.WorkflowRoot;
                if (null == wfRoot)
                {
                    throw new ArgumentException("Workflow root is null");
                }
                else
                {
                    wfRoot.Refresh();
                    categories.Add(wfRoot);
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex, TridentErrorConstants.ErrorNumber1300000001, ex.Message);
            }
        }

        /// <summary>
        /// Gets the users my workflows.
        /// </summary>
        /// <param name="categories">The categories.</param>
        public void GetMyWorkflowsForCurrentUser(Collection<Namespace> categories)
        {
            RegistryService.GetMyWorkflowsForUser(categories, this.currentUser);
        }

        /// <summary>
        /// Gets all users my workflows.
        /// </summary>
        /// <param name="categories">The categories.</param>
        public void GetAllUsersMyWorkflows(Collection<Namespace> categories)
        {
            List<User> allUsers = User.LoadAll(this.registryConnection);
            foreach (User user in allUsers)
            {
                if ((user as IObject).ID != (this.currentUser as IObject).ID)
                {
                    RegistryService.GetMyWorkflowsForUser(categories, user);
                }
            }
        }

        /// <summary>
        ///  Retrieve the nodes in which the executer service is running.
        /// </summary>
        /// <returns>Collection of nodes.</returns>
        public Collection<MachineModel> GetAllMachines(bool isInteractive)
        {
            return this.GetNodes(isInteractive);
        }

        /// <summary>
        /// Retrieve the authenticated users from the registry.
        /// </summary>
        /// <returns>The collection of authenticated users.</returns>
        public Collection<User> GetAllAuthenticatedUsers()
        {
            Collection<User> authenticatedUsers = new Collection<User>();
            try
            {
                Collection<User> userList = User.CommonSearches.GetAllEnabledUsers(this.registryConnection);
                foreach (User user in userList)
                {
                    // Check if it is an authenticated user.
                    if (IsAuthenticatedUser(user))
                    {
                        authenticatedUsers.Add(user);
                    }
                    else
                    {
                        // Check if any one of the groups he belongs to is authenticated.
                        foreach (Group group in user.Groups)
                        {
                            if (IsAuthenticatedUser(group))
                            {
                                authenticatedUsers.Add(user);
                                break;
                            }
                        }
                    }
                }
                return authenticatedUsers;
            }
            catch (RecordNotFoundException)
            {
                return null;
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex,
                   TridentErrorConstants.ErrorNumber1000000071,
                   ManagementStudioResourceManager.GetString("UsersRetrieveError"));
            }
            return null;
        }

        /// <summary>
        /// Retrieve the guest users from the registry.
        /// </summary>
        /// <returns>The collection of guest users.</returns>
        public Collection<User> GetAllGuests()
        {
            Collection<User> guestUsers = new Collection<User>();
            try
            {
                Collection<User> userList = User.CommonSearches.GetAllEnabledUsers(this.registryConnection);
                foreach (User user in userList)
                {
                    bool isGuest = true;
                    // Check if the user is a guest.
                    if (!IsAuthenticatedUser(user))
                    {
                        // Check if any one of the groups the user belongs to is a guest.
                        foreach (Group group in user.Groups)
                        {
                            if (IsAuthenticatedUser(group))
                            {
                                isGuest = false;
                                break;
                            }
                        }
                    }
                    else
                    {
                        isGuest = false;
                    }
                    if (isGuest)
                    {
                        guestUsers.Add(user);
                    }
                }
            }
            catch (RecordNotFoundException)
            {
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex,
                   TridentErrorConstants.ErrorNumber1000000070,
                   ManagementStudioResourceManager.GetString("UsersRetrieveError"));
            }
            return guestUsers;
        }



        /// <summary>
        /// Retreive the workflow whose guid is specified.
        /// </summary>
        /// <param name="workflowId">The ID of the required workflow</param>
        /// <returns>The workflow required. </returns>
        public Activity GetWorkflow(Guid workflowId)
        {
            if (workflowId == Guid.Empty)
            {
                throw new ArgumentNullException("workflowId");
            }

            return this.GetWorkflowById(workflowId);
        }

        /// <summary>
        /// Gets all shared workflows.
        /// </summary>
        /// <returns></returns>
        public ICollection<Activity> GetAllSharedWorkflowsForCurrentUser()
        {
            List<Activity> allPermittedActivities = new List<Activity>();
            try
            {
                allPermittedActivities = Activity.LoadAll(this.registryConnection).Where(
                    activity => this.CheckActivityValidityForNormalUser(activity)).ToList();
            }
            catch (RecordNotFoundException) { }
            catch (Exception ex)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(ex);
            }

            return allPermittedActivities;
        }

        /// <summary>
        /// Fetches the current user.
        /// </summary>
        /// <returns></returns>
        public User FetchCurrentUser()
        {
            return this.currentUser;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets the users my workflows.
        /// </summary>
        /// <param name="categories">The categories.</param>
        private static void GetMyWorkflowsForUser(Collection<Namespace> categories, User user)
        {
            try
            {
                if (user != null)
                {
                    Namespace userRoot = user.WorkflowRoot;
                    if (userRoot != null)
                    {
                        userRoot.Refresh();
                        Namespace currentUserRoot = userRoot.Children.Where(
                            child => child.User != null).FirstOrDefault(
                            child => child.User.Equals(user));

                        if (currentUserRoot != null)
                        {
                            categories.Add(currentUserRoot);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex, TridentErrorConstants.ErrorNumber1300000001, ex.Message);
            }
        }

        /// <summary>
        /// Checks the activity validity.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <returns></returns>
        private bool CheckActivityValidityForNormalUser(Activity activity)
        {
            bool validActivity = false;
            if (!activity.IsDeleted
                && activity.IsWorkflow
                && this.currentUser != null
                && this.systemUserObject != null
                && !activity.Owner.Equals(this.currentUser)
                && !activity.Owner.Equals(this.systemUserObject)
                && (activity.Security.Rights.AuthorWorkflow.Check(this.currentUser)
                    || activity.Security.Rights.RunWorkflow.Check(this.currentUser)))
            {
                validActivity = true;
            }

            return validActivity;
        }

        /// <summary>
        /// Gets the system user.
        /// </summary>
        /// <returns>
        /// The system user.
        /// </returns>
        private User GetUser(string userName)
        {
            User user = null;
            try
            {
                user = User.Load(userName, this.registryConnection);
            }
            catch (RecordNotFoundException ex)
            {
                TridentErrorHandler.HandleUIException(ex);
            }

            return user;
        }

        /// <summary>
        /// Retrieve the nodes in which the executer service is running.
        /// </summary>
        /// <returns>List of machines.</returns>
        private Collection<MachineModel> GetNodes(bool isInteractive)
        {
            Collection<MachineModel> machineModels = new Collection<MachineModel>();
            try
            {
                Machine.ISearch search = Machine.ISearch.Create();
                Machine.ISearch.ISearchClause isDeletedCondition = Machine.ISearch.IsDeleted(BooleanField.Condition.Equals, false);
                Machine.ISearch.ISearchClause typeCond = Machine.ISearch.IsInteractiveExecution(BooleanField.Condition.Equals, isInteractive);
                Machine.ISearch.ISearchClause availabilityCondition = Machine.ISearch.Availability(EnumFieldBase.Condition.Equals, ExecutorAvailabiltiy.Online);
                Machine.ISearch.ISearchClause vmCondition = Machine.ISearch.Type(EnumFieldBase.Condition.NotEquals, MachineType.Virtual);

                Machine.ISearch.ISearchGroup searchClause = Machine.ISearch.AND(isDeletedCondition, typeCond, availabilityCondition, vmCondition);
                search.Query = searchClause;

                Machine.Search(search, this.CurrentConnection)
                    .ForEach(item => machineModels.Add(new MachineModel(item)));
            }
            catch (RecordNotFoundException ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex,
                    TridentErrorConstants.ErrorNumber1000000011,
                    ManagementStudioResourceManager.GetString("MachineRetrieveError"));
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex,
                   TridentErrorConstants.ErrorNumber1000000011,
                   ManagementStudioResourceManager.GetString("MachineRetrieveError"));
            }

            return machineModels;
        }

        /// <summary>
        /// Retrieve the required workflow from registry.
        /// </summary>
        /// <param name="workflowId">The workflow ID.</param>
        /// <returns>The workflow object.</returns>
        private Activity GetWorkflowById(Guid workflowId)
        {
            try
            {
                Activity workflow = Activity.Load(workflowId, this.registryConnection);
                if (workflow != null && workflow.IsDeleted)
                {
                    workflow = null;
                }

                return workflow;
            }
            catch (RecordNotFoundException ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex,
                    TridentErrorConstants.ErrorNumber1000000012,
                    ManagementStudioResourceManager.GetString("WorkflowRetrieveError"));
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(ex);
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex,
                   TridentErrorConstants.ErrorNumber1000000012,
                   ManagementStudioResourceManager.GetString("WorkflowRetrieveError"));
            }
            return null;
        }

        /// <summary>
        /// Determines whether [is authenticated user] [the specified principal].
        /// </summary>
        /// <param name="principal">The principal.</param>
        /// <returns>
        /// 	<c>true</c> if [is authenticated user] [the specified principal]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsAuthenticatedUser(ISecurityPrincipal principal)
        {
            SystemPolicy policy = SystemPolicy.Get(this.registryConnection);
            bool isAuthenticated = false;
            if (policy.Builtin.Administrators.Contains(principal) || policy.Builtin.Services.Contains(principal) || policy.Builtin.Users.Contains(principal))
            {
                isAuthenticated = true;
            }
            return isAuthenticated;
        }

        #endregion Private Methods
    }
}
